package com.cloud.common.limit;

import com.cloud.common.bean.ResultInfo;
import com.cloud.common.context.AppContext;
import com.cloud.common.context.RequestContext;
import com.cloud.common.context.SessionContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

/**
 * 访问频率限制拦截器
 *
 * @author fengwenjin
 */
@Slf4j
@Aspect
@Component
@ConditionalOnMissingClass("com.cloud.gateway.bean.GatewayInfo")
public class LimitInterceptor {
    /**
     * 处理限制逻辑
     *
     * @param point
     * @return Object
     * @throws Throwable
     */
    @Around("@annotation(Limit)")
    public Object doLimit(ProceedingJoinPoint point) throws Throwable {
        Limit limit = getLimit(point);
        if (limit != null) {
            // 构建缓存key
            final String keyPrefix = "Limit:" + AppContext.getAppName() + ":" + RequestContext.getRequest().getRequestURI();
            String limitKey = keyPrefix + ":" + RequestContext.getRequestIP();
            if (limit.limitMode() == 1) {
                limitKey = keyPrefix + ":" + SessionContext.getAppName() + "@" + SessionContext.getUserId();
            }
            // 读取限制信息
            RedisTemplate<String, Object> redisTemplate = getRedisTemplate();
            LimitInfo limitInfo = (LimitInfo) redisTemplate.opsForValue().get(limitKey);
            if (limitInfo == null) {
                limitInfo = new LimitInfo();
                limitInfo.setCount(0);
                limitInfo.setBeginTime(System.currentTimeMillis());
            }
            // 检查是否超过限制
            int count = limitInfo.getCount() + 1;
            if (count > limit.limitCount()) {
                log.warn("limitKey >>> {}", limitKey);

                MethodSignature signature = (MethodSignature) point.getSignature();
                Type returnType = signature.getMethod().getGenericReturnType();
                if (returnType == String.class) {
                    return "error";
                }
                return ResultInfo.manyRequest().setMessage(limit.message());
            }
            limitInfo.setCount(count);
            redisTemplate.opsForValue().set(limitKey, limitInfo, limit.durationSeconds(), TimeUnit.SECONDS);
        }
        return point.proceed();
    }

    private Limit getLimit(ProceedingJoinPoint point) {
        Limit limit = null;
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Annotation[] annotations = methodSignature.getMethod().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof Limit) {
                limit = (Limit) annotation;
                break;
            }
        }
        return limit;
    }

    private static RedisTemplate<String, Object> getRedisTemplate() {
        return (RedisTemplate<String, Object>) AppContext.getBean(RedisTemplate.class);
    }
}
